/** @file    TestDeviceBundle.cpp
 *  @author  Alessandro Polo
 *  @version $Id: TestDeviceBundle.cpp 2873 2010-08-08 19:06:41Z alex $
 *  @brief
 * File containing methods for the wosh::devices::TestDeviceBundle class.
 * The header for this class can be found in TestDeviceBundle.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include "TestDeviceBundle.h"
 #include "TestDeviceImpl.h"

 #include <core/ObjectAllocator.h>
 #include <core/MethodsCommon.h>
 #include <core/Fact.h>
 #include <core/ThreadManager.h>
 #include <core/DeviceManager.h>
 #include <core/WoshKernel.h>

 #include <core/MessageFilter.h>
 #include <core/MessageProcessingCommon.h>

using namespace std;
namespace wosh {
 namespace devices {

 WOSH_REGISTER(wosh::devices::TestDeviceBundle, "wosh::interfaces::Device", _TestDevice_VERSION, _static_TestDeviceBundle1 )
 WOSH_REGISTER(wosh::devices::TestDeviceBundle, "wosh::BundleGeneric", _TestDevice_VERSION, _static_TestDeviceBundle2 )

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

TestDeviceBundle::TestDeviceBundle()
	: BundleGeneric() {
	BundleGeneric::setName( _TestDevice_NAME, false );
	Properties.update( _KEY_Version, _TestDevice_VERSION );

	Log(LOG_DEBUG, " Configuring TestDevice worker.." );
	this->devWorker = new TestDeviceImpl(*this);
	this->devWorker->setTestDeviceListener(this);

	this->devState = wosh::interfaces::Device::UNKNOWN;

	Log(LOG_DEBUG, " Setting default properties and permissions.." );

	Properties.set( _KEY_name, Object::getName(), Permission( Permission::RW, Permission::Read, Permission::Read  ) );
	Properties.set( _KEY_type, Device::bundleType(), Permission( Permission::RW, Permission::Read, Permission::Read  ) );
	Properties.set( _KEY_interfaces, Interfaces.toString(), Permission( Permission::RW, Permission::Read, Permission::Read  ) );


//	Properties.set( _TestDevice_KEY_RefreshFrequency,	1,									Permission::RW_RW_R__() );
	Properties.set( _Device_KEY_DeviceStatus,		wosh::interfaces::Device::getDeviceStateAsString(wosh::interfaces::Device::UNKNOWN), Permission( Permission::Read ) );

	Properties.set( _TestDevice_KEY_RegisterAsDevice, true, Permission( Permission::RW, Permission::Read, Permission::Read ) );
	Properties.monitor(_KEY_type);
	Properties.monitor(_KEY_interfaces);
	Properties.monitor(_TestDevice_KEY_RegisterAsDevice);

//	Properties.set( "State", "Playing", Permission( Permission::RWX) );

	Log(LOG_DEBUG, " Registering methods.." );
	MethodRequest* mmRaiseEvent = Methods.create<MethodRequest>( _TestDevice_METHOD_raise_event );
	mmRaiseEvent->setMethod( this, (MethodRequestPtr)&TestDeviceBundle::mmDoRaiseEvent );
	mmRaiseEvent->getPermission().setMask( Permission::RX, Permission::RX, Permission::RX );

	MethodRequest* mmForceState = Methods.create<MethodRequest>( _TestDevice_METHOD_force_state );
	mmForceState->setMethod( this, (MethodRequestPtr)&TestDeviceBundle::mmDoForceState );
	mmForceState->getPermission().setMask( Permission::RX, Permission::RX, Permission::RX );

	MethodMessageResponse* mmWaitMethod = Methods.create<MethodMessageResponse>( _TestDevice_METHOD_wait );
	mmWaitMethod->setMethod( this, (MethodMessageResponsePtr)&TestDeviceBundle::mmDoWaitMethod );
	mmWaitMethod->getPermission().setMask( Permission::RX, Permission::RX, Permission::RX );

	setBundleState(Bundle::STATE_CREATED, false);
 }


TestDeviceBundle::~TestDeviceBundle() {
	Log(LOG_INFO, " Destroying.." );
	if ( isBundleRunning() ) {
		Log(LOG_WARNING, "~TestDeviceBundle() : Destroying while Bundle is running! Trying to stop.." );
		bundleStop();
	 }
	if ( this->devWorker->isThreadRunning() ) {
		Log(LOG_WARNING, "~TestDeviceBundle() : Destroying while RUNNING! Trying to stop.." );
		this->devWorker->quitThread();
		this->devWorker->waitThread(30000);
	 }
	delete this->devWorker; this->devWorker = NULL;
	Log(LOG_VERBOSE, ":~TestDeviceBundle() : Destroyed." );
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////// BUNDLE CONTROL

WRESULT TestDeviceBundle::bundleStart() {
	if ( !BundleGeneric::bundleValidate_StartStop(Bundle::STATE_STARTING) ) return WRET_ERR_WRONG_STATE;

	WRESULT ret = BundleGeneric::start_SynchThread( this->devWorker );

	BusDevices.setMessageHandler(this);
	WRESULT bus_connected = BusDevices.connect( _Bus_Devices, Bus::ACTION_DEFERRED | Bus::BUS_CREATE_IF_MISSING );
	if ( WFAILED(bus_connected) )
		Log(LOG_CRITICAL, ":TestDeviceBundle() : FAILED#%d Connecting Bus ("_Bus_Devices")..", bus_connected );

	if ( WSUCCEEDED(ret) && Properties.getValueOf(_TestDevice_KEY_RegisterAsDevice).toBoolean(false) ) {
		Log(LOG_VERBOSE, ":bundleStart() : Registering as Device.." );
		wosh::Device* myself = new wosh::Device( *this );
		myself->getInterfaces() = this->Interfaces;
		WoshKernel::getInstance()->devices().registerDevice( myself );
	 }
	// BUNDLE-STATE (STARTED) will be updated async by WORKER, through call: IThreadListener::thread_event()
	return ret;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT TestDeviceBundle::bundleStop() {
	if ( !BundleGeneric::bundleValidate_StartStop(Bundle::STATE_STOPPING) ) return WRET_ERR_WRONG_STATE;

	WRESULT ret = BundleGeneric::stop_SynchThread( this->devWorker );

	if ( WSUCCEEDED(ret) ) {
		Log(LOG_VERBOSE, ":bundleStart() : UnRegistering as Device.." );
		WoshKernel::getInstance()->devices().unRegisterDevice( this->getName() );
	 }
	// BUNDLE-STATE (STOPPED) will be updated async by WORKER, through call: IThreadListener::thread_event()
	return ret;
 }

WRESULT TestDeviceBundle::bundleDiagnostic() {
	Log(LOG_VERBOSE, ":bundleDiagnostic() : Starting TestDeviceBundle-Diagnostic" );

	WRESULT ret = BundleGeneric::bundleDiagnostic();

	Log(LOG_INFO, ":bundleDiagnostic() : Finished TestDeviceBundle-Diagnostic [%d]", ret );
	return ret;
 }

////////////////////////////////////////////////////////////////////////////////////////////// BUNDLE CONTROL
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////// PROCESS BUS MESSAGES

void TestDeviceBundle::busMessage( const Message& message, const Bus* source ) {
	DeviceManager::busMessageForBundleDevices( message, source, this, this->Methods, this->Log, this->BusCore, this->BusDevices );
 }

//////////////////////////////////////////////////////////////////////////////////////// PROCESS BUS MESSAGES
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENTS

bool TestDeviceBundle::updatingProperty( bool& do_update, const Variant& value_proposed, Property& property_current, const PropertiesProvider* source )
 {
	if ( property_current.getKey() == _KEY_type ) {
		Log(LOG_VERBOSE, " Changing Bundle Type to '%s'", value_proposed.toString().c_str() );
		return true;
	 }
	else if ( property_current.getKey() == _KEY_interfaces ) {
		Log(LOG_VERBOSE, " Changing Bundle Interfaces to '%s'", value_proposed.toString().c_str() );
		this->Interfaces.fromString(value_proposed.toString());
		return true;
	 }
	else if ( property_current.getKey() == _TestDevice_KEY_RegisterAsDevice ) {
		bool reg = value_proposed.toBoolean(false);
		if ( this->devWorker->isThreadRunning() ) {
			if ( reg ) {
				Log(LOG_VERBOSE, ":updatingProperty() : Registering as Device.." );
				wosh::Device* myself = new wosh::Device( *this );
				WoshKernel::getInstance()->devices().registerDevice( myself );
			 }
			else {
				Log(LOG_VERBOSE, ":updatingProperty() : UnRegistering as Device.." );
				WoshKernel::getInstance()->devices().unRegisterDevice( this->getName() );
			 }
		 }
		else
			Log(LOG_VERBOSE, " Register as Device '%s'", value_proposed.toString().c_str() );
		return true;
	 }
	else // forward processing to base class
		return BundleGeneric::updatingProperty(do_update, value_proposed, property_current, source);
 }

///////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENTS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////// METHODS

Response* TestDeviceBundle::mmDoForceState( const Request* request )
 {
	if ( request == NULL ) return NULL;
	string option = request->getArgument().toString();
	Log(LOG_VERBOSE, ":mmDoForceState(%s)", option.c_str() );

	return request->replyResponse(WRET_OK );
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Response* TestDeviceBundle::mmDoRaiseEvent( const Request* request ) {
	if ( request == NULL ) return NULL;
	Variant dEventName;
	Variant dEventData;
	request->extractArguments(dEventName, dEventData);
	if ( !dEventName.isStringNotEmpty() || dEventData.isEmpty() )
		return request->replyResponse(WRET_ERR_PARAM, "Invalid arguments [username, location]");
	Log(LOG_VERBOSE, "mmDoFakeDeviceEvent() Fact: %s [%s]", dEventName.asString().c_str(), dEventData.toString().c_str() );
	Message* message = new Message( new Fact(dEventName.asString(), dEventData.clone()) );
	message->setSource(this);
	message->setDestinationBroadcast(true);
	BusCore.postMessage(message);
	return request->replyResponse(WRET_OK, "Device-Event (message) sent!" );
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Response* TestDeviceBundle::mmDoWaitMethod( const Message* requestMessage ) {
	if ( requestMessage == NULL || requestMessage->isEmpty() ) return NULL;
	if ( !requestMessage->getContent()->isRequest() ) return NULL;
	const Request* request = requestMessage->getContent()->asRequest();
	int time = request->getArgument().toInteger(0);
	Log(LOG_WARNING, ":mmDoWaitMethod() Blocking Bus %s for %d seconds", requestMessage->getCurrentBus().c_str(), time );
	ThreadImpl::sleepForSec(time);
	Log(LOG_INFO, ":mmDoWaitMethod() Releasing Bus %s after %d seconds", requestMessage->getCurrentBus().c_str(), time );
	return request->replyResponse(WRET_OK);
 }

///////////////////////////////////////////////////////////////////////////////////////////////////// METHODS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

 }; // namespace devices
}; // namespace wosh
